<template>
    <div class="image-viewer card d-flex flex-column">
        <div :class="['viewer-container flex-grow-1', transitionClass]" :style="containerStyle">
            <div :class="['image-outer-box', transitionClass]" :style="[imageStyle.outer]">
                <div :class="['image-rotator-box', transitionClass]" :style="[imageStyle.rotator]">
                    <img :src="cur_src" :alt="loadFailedMessage" :class="[transitionClass]" :style="imageStyle.image"/>
                </div>
            </div>
        </div>
        <div class="viewer-bar card-footer d-flex flex-shrink-0 p-2 align-items-center flex-wrap">
            <slot name="footer-bar">
                
            </slot>
            <!-- Default dropup button -->
            <div class="btn-group dropup d-inline-block ml-auto">
              <button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                {{viewMode}}
              </button>
              <div class="dropdown-menu p-0">
                <button class="dropdown-item btn my-1" href="#" @click.prevent="$emit('update:viewMode', mode)" v-for="mode in MODES">{{mode}}</button>
              </div>
            </div>
            <button
                type="button"
                class="btn btn-sm btn-outline-dark ml-2"
                @click="scale(ratio / 2, {clientX: containerCenter[0], clientY: containerCenter[1]})"
            >
                <i class="material-icons align-middle">zoom_in</i>
            </button>
            <button
                type="button"
                class="btn btn-sm btn-outline-dark ml-2"
                @click="scale(-ratio / 2, {clientX: containerCenter[0], clientY: containerCenter[1]})"
            >
                <i class="material-icons align-middle">zoom_out</i>
            </button>
            <button
                type="button"
                class="btn btn-sm btn-outline-dark ml-2"
                @click="rotate(-90)"
            >
                <i class="material-icons align-middle">rotate_90_degrees_ccw</i>
            </button>
            <button
                type="button"
                class="btn btn-sm btn-outline-dark ml-2"
                @click="rotate(90)"
            >
                <i class="material-icons align-middle"  style="transform: scaleX(-1);">rotate_90_degrees_ccw</i>
            </button>
            <button
                type="button"
                class="btn btn-sm btn-outline-dark ml-2"
                @click="toggleMirrorHorizontal"
            >
                <i class="material-icons align-middle">flip</i>
            </button>
            <button
                type="button"
                class="btn btn-sm btn-outline-dark ml-2"
                @click="toggleMirrorVertical"
            >
                <i class="material-icons align-middle" style="transform: rotateZ(90deg);">flip</i>
            </button>
            <button
                type="button"
                class="btn btn-sm btn-outline-dark ml-2"
                @click="initImage"
            >
                <i class="material-icons align-middle">refresh</i>
            </button>
            
            <div class="btn-group dropup ml-2">
                <button
                    type="button"
                    class="btn btn-sm btn-outline-dark"
                    @click="centerImage"
                >
                    <i class="material-icons align-middle">center_focus_strong</i>
                </button>
              <button type="button" class="btn btn-outline-dark dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                <span class="sr-only">Toggle Dropdown</span>
              </button>
              <div class="dropdown-menu p-0" style="min-width: 9rem;">
                <div class="p-2 d-flex align-items-center justify-content-around">
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_UP, constant.FOCUS_LEFT)">
                        <i class="material-icons align-middle" style="transform: rotateZ(-45deg)">expand_less</i>
                    </button>
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_UP)">
                        <i class="material-icons align-middle">expand_less</i>
                    </button>
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_UP, constant.FOCUS_RIGHT)">
                        <i class="material-icons align-middle" style="transform: rotateZ(45deg)">expand_less</i>
                    </button>
                </div>
                <div class="p-2 d-flex align-items-center justify-content-around">
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_LEFT)">
                        <i class="material-icons align-middle">chevron_left</i>
                    </button>
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_CENTER)">
                        <i class="material-icons align-middle">lens</i>
                    </button>
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_RIGHT)">
                        <i class="material-icons align-middle">chevron_right</i>
                    </button>
                </div>
                <div class="p-2 d-flex align-items-center justify-content-around">
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_DOWN, constant.FOCUS_LEFT)">
                        <i class="material-icons align-middle" style="transform: rotateZ(45deg)">expand_more</i>
                    </button>
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_DOWN)">
                        <i class="material-icons align-middle">expand_more</i>
                    </button>
                    <button class="p-0 btn btn-outline-secondary btn-sm" @click.prevent.stop="focus(constant.FOCUS_DOWN, constant.FOCUS_RIGHT)">
                        <i class="material-icons align-middle" style="transform: rotateZ(-45deg)">expand_more</i>
                    </button>

                </div>
              </div>
            </div>

            <button type="button" class="btn btn-sm btn-outline-dark mx-2 ml-auto" @click="$emit('close')">
                关闭
            </button>
        </div>
        <loading
        v-if="useThumb || !useAsync"
        :active.sync="isLoading"
        :can-cancel="false"
        backgroundColor='transparent'
        :is-full-page="false"></loading>
    </div>
</template>
<script type="text/javascript">
import _ from 'lodash'
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/vue-loading.css';
var constant = {
    UNLOAD: "unload", LOADED: "loaded", LOADFAIL: "error",
    BEFORE_LOAD: "before-load", IMAGE_LOADED: "loaded", IMAGE_ERROR: "error",
    VIEW_MODE_ORIGINAL: "原始图像", VIEW_MODE_AUTO: "自适应", VIEW_MODE_CONTAIN: "适合", VIEW_MODE_COVER: "填充",
    VIEW_MODE_WIDTH_FULL: "宽度铺满", VIEW_MODE_HEIGHT_FULL: "高度铺满",
    FOCUS_UP: 'up', FOCUS_DOWN: 'down', FOCUS_LEFT: 'left', FOCUS_RIGHT: 'right', FOCUS_CENTER: 'center'
}


export default {
    name: 'image-viewer',
    components: {
        Loading
    },
    props: {
        src: {
            type: String,
            default: '',
            required: true
        },
        alt: {
            type: String,
            default: ''
        },
        viewMode: {
            type: String,
            default: constant.VIEW_MODE_AUTO
        },
        transition: {
            type: Boolean,
            default: true
        },
        minWidth: {
            type: Number,
            default: 100
        },
        plugins: {
            type: Array,
            default: function() {
                return [];
            }
        },
        useThumb: {
            type: Boolean,
            default: true
        },
        thumbUrl: {
            type: Function,
            default: function(src) {
                return src + '?size=80';
            }
        },
        loadingUrl: {
            type: String,
            default: '/static/loading.svg'
        },
        useAsync: {
            type: Boolean,
            default: true
        }
    },
    data: function() {
        var vm = this;
        return {
            thumbState: constant.UNLOAD,
            asyncLoading: this.useAsync,
            asyncLoaded: false,

            ignoreTransition: false,
            pluginDataSet: {},

            state: constant.UNLOAD,

            containerSize: {
                width: 0,
                height: 0
            },
            containerCenter: [0, 0],
            imageSize: {
                width: 0,
                height: 0
            },
            
            mirror: {
                horizontal: false,
                vertical: false
            },
            ratio: 1,
            degree: 0,
            offset: {
                horizontal: 0,
                vertical: 0,
                width: 0,
                height: 0
            },
            imageListener: {
                'load': function() {
                    vm.$nextTick(() => {
                        setTimeout(function() {
                            if (vm.useThumb && vm.isThumbImage) {
                                vm.thumbState = constant.LOADED;
                            } else if (!vm.isLoadingImage) {
                                vm.state = constant.LOADED;
                                vm.$emit(constant.IMAGE_LOADED, vm);
                            }
                            
                        }, 100);
                    })
                    vm.initImage();
                },
                'error': function() {
                    if (vm.useThumb && vm.isThumbImage) {
                        vm.thumbState = constant.LOADFAIL;
                    } else if (!vm.isLoadingImage) {
                        vm.state = constant.LOADFAIL;
                        vm.$emit(constant.IMAGE_ERROR, vm);
                    }
                    
                }
            },
            constant
        }
    },
    computed: {
        cur_src: function() {
            if (!this.useAsync) {
                return this.src;
            }
            if (this.asyncLoaded) {
                return this.src;
            }
            if (this.isAsyncLoading || this.thumbState == constant.UNLOAD) {
                return this.useThumb ? this.thumbUrl(this.src) : this.loadingUrl;
            } else {
                return this.src;
            }
        },
        isAsyncLoading() {
            return this.useAsync ? this.asyncLoading : false;
        },
        isThumbImage() {
            return this.cur_src == this.thumbUrl(this.src);
        },
        isLoadingImage() {
            return this.cur_src == this.loadingUrl;
        },
        MODES: function() {
            return [
                constant.VIEW_MODE_AUTO, constant.VIEW_MODE_CONTAIN, constant.VIEW_MODE_COVER,
                constant.VIEW_MODE_ORIGINAL, constant.VIEW_MODE_WIDTH_FULL, constant.VIEW_MODE_HEIGHT_FULL
            ]
        },
        isLoading: function() {
            return this.state == constant.UNLOAD;
        },
        initedRatio: function() {
            var vm = this,
                width = (vm.imageSize.width || vm.imageSize.minWidth),
                height = (vm.imageSize.height || vm.imageSize.minWidth);
            switch (vm.viewMode) {
                case constant.VIEW_MODE_AUTO:
                    // if (width < vm.containerSize.width && height < vm.containerSize.height) {
                    //     return 1;
                    // }
                    return Math.min(
                        vm.containerSize.width / width,
                        vm.containerSize.height / height
                    ) * 0.85;
                case constant.VIEW_MODE_CONTAIN:
                    return Math.min(
                        vm.containerSize.width / width,
                        vm.containerSize.height / height
                    )
                case constant.VIEW_MODE_COVER:
                    return Math.max(
                        vm.containerSize.width / width,
                        vm.containerSize.height / height
                    );
                case constant.VIEW_MODE_ORIGINAL:
                    return 1;
                case constant.VIEW_MODE_WIDTH_FULL:
                    return vm.containerSize.width / width;
                case constant.VIEW_MODE_HEIGHT_FULL:
                    return vm.containerSize.height / height;
                default:
                    return 1;
            }
        },
        loadFailedMessage: function() {
            return this.alt ? this.alt : this.src;
        },
        imageWidth: function() {
            var vm = this;
            return Math.max(vm.imageSize.width * vm.ratio, vm.minWidth);
        },
        imageHeight: function() {
            var vm = this;
            var ratio = vm.imageWidth / (vm.imageSize.width || vm.minWidth);
            return vm.imageSize.height * ratio;
        },
        containerStyle: function() {
            return {
                width: '100%',
                height: '100%',
                position: 'relative',
                overflow: 'hidden'
            }
        },
        imageStyle: function() {
            var vm = this;
            return {
                image: {
                    width: '100%',
                    height: '100%',
                    transform: `
                        scale(
                            ${vm.mirror.horizontal ? -1 : 1}, ${vm.mirror.vertical ? -1 : 1}
                        )
                        translateZ(0)
                    `
                },
                outer: {
                    position: 'absolute',
                    top: 0,
                    left: 0,
                    fontSize: 0,
                    width: vm.imageSize.width + 'px',
                    height: vm.imageSize.height + 'px',
                    transformOrigin: `0 0`,
                    transform: `translate3d(${vm.offset.horizontal}px, ${vm.offset.vertical}px, 0) scale(${vm.ratio})`
                },
                rotator: {
                    width: '100%',
                    height: '100%',
                    transformOrigin: `50% 50%`,
                    transform: `rotate(${vm.degree}deg) translateZ(0)`
                }
            }
        },
        transitionClass: function() {
            var vm = this;
            return {
                transition: vm.state == constant.LOADED && !vm.ignoreTransition && vm.transition
            }
        }
    },
    methods: {
        initImage: function() {
            var vm = this;
            vm.freshContainerSize();
            vm.imageSize.width = vm.image.naturalWidth;
            vm.imageSize.height = vm.image.naturalHeight;
            vm.ratio = vm.initedRatio;
            vm.mirror.horizontal = false;
            vm.mirror.vertical = false;
            vm.degree = 0;
            vm.offset.width = vm.offset.height = 0;
            vm.offset.horizontal = (vm.containerSize.width - vm.imageWidth) / 2;
            vm.offset.vertical = (vm.containerSize.height - vm.imageHeight) / 2;
            vm.plugins.forEach(x => x.init && x.init(vm, vm.pluginDataSet[x.name]));
        },
        getViewSize() {
            var vm = this;
            var rect = (vm.image && vm.image.getBoundingClientRect) ?
                        vm.image.getBoundingClientRect() : null;
            var size = rect ? {
                width: rect.right - rect.left,
                height: rect.bottom - rect.top
            } : {
                width: vm.imageWidth,
                height: vm.imageHeight
            };
            vm.offset.width = (size.width - vm.imageWidth);
            vm.offset.height = (size.height - vm.imageHeight);
            return size;
        },
        focus: function() {
            var vm = this;
            var size = vm.getViewSize();
            for (var i = 0; i < arguments.length; i++) {
                var which = arguments[i];
                switch (which) {
                    case constant.FOCUS_UP:
                        vm.move(0, -vm.offset.vertical + vm.offset.height / 2);
                        break;
                    case constant.FOCUS_DOWN:
                        vm.move(0, (vm.containerSize.height - size.height) - vm.offset.vertical + vm.offset.height / 2);
                        break;
                    case constant.FOCUS_LEFT:
                        vm.move(-vm.offset.horizontal + vm.offset.width / 2, 0);
                        break;
                    case constant.FOCUS_RIGHT:
                        vm.move((vm.containerSize.width - size.width) - vm.offset.horizontal + vm.offset.width / 2, 0);
                        break;
                    case constant.FOCUS_CENTER:
                        vm.moveTo((vm.containerSize.width - size.width) / 2, (vm.containerSize.height - size.height) / 2);
                        break;
                }
            }
            
        },
        centerImage: function() {
            this.focus(constant.FOCUS_CENTER);
        },
        scale: function(inc_ratio, origin) {
            // origin: {x,y} relative to imageBox boundingClientRect
            var vm = this;
            var rect = vm.imageBox.getBoundingClientRect();
            if (!origin) {
                var rect1 = vm.image.getBoundingClientRect();
                    
                origin = {
                    x: (rect1.left + rect1.right) / 2 - rect.left,
                    y: (rect1.top + rect1.bottom) / 2 - rect.top
                };
            } else if (origin.clientX !== undefined && origin.clientY !== undefined) {
                origin = {
                    x: origin.clientX - rect.left,
                    y: origin.clientY - rect.top
                };
            }
            var ratio = vm.ratio;
            vm.scaleTo(vm.ratio + inc_ratio);
            var r = (1 - vm.ratio / ratio);
            vm.move(origin.x * r, origin.y * r);
        },
        scaleTo: function(ratio) {
            var vm = this;
            ratio = Math.max(ratio, vm.minWidth / vm.imageSize.width);
            vm.ratio = ratio;
        },
        move: function(inc_x, inc_y) {
            // 相对偏移
            var vm = this;
            vm.getViewSize();
            vm.moveTo(
                vm.offset.horizontal + inc_x - vm.offset.width / 2,
                vm.offset.vertical + inc_y - vm.offset.height / 2
            );
        },
        moveTo: function(x, y) {
            // 变换后偏移
            var vm = this;
            vm.getViewSize();
            vm.offset.horizontal = vm.offset.width / 2 + x;
            vm.offset.vertical = vm.offset.height / 2 + y;
        },
        rotate: function(inc_degree) {
            var vm = this;
            vm.rotateTo(vm.degree + inc_degree);
        },
        rotateTo: function(degree) {
            var vm = this;
            vm.degree = degree;
        },
        toggleMirrorHorizontal: function() {
            var vm = this;
            vm.mirror.horizontal = !vm.mirror.horizontal;
        },
        toggleMirrorVertical: function() {
            var vm = this;
            vm.mirror.vertical = !vm.mirror.vertical;
        },
        tryAsyncLoad() {
            var vm = this;
            if (!vm.useAsync) {
                return;
            }
            vm.asyncLoading = true;
            vm.asyncLoaded = false;
            var src = vm.src;
            var img = new Image();
            img.addEventListener('load', function() {
                if (src === vm.src) {
                    vm.asyncLoading = false;
                    vm.asyncLoaded = true;
                }
            });
            img.addEventListener('error', function() {
                if (src === vm.src) {
                    vm.asyncLoading = false;
                    vm.asyncLoaded = true;
                }
            });
            img.src = src;
        },
        freshContainerSize() {
            var vm = this;
            var rect = vm.container.getBoundingClientRect();
            vm.containerSize.width = vm.container.clientWidth;
            vm.containerSize.height = vm.container.clientHeight;
            vm.containerCenter = [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];
        }
    },
    created: function() {

    },
    mounted: function() {
        var vm = this;
        vm.container = vm.$el.querySelector('.viewer-container');
        vm.imageBox = vm.container.children[0];
        vm.image = vm.$el.querySelector('img');

        vm.freshContainerSize();

        vm.plugins.forEach(x => {
            vm.$set(vm.pluginDataSet, x.name, {});
            x.bind && x.bind(vm, vm.pluginDataSet[x.name]);  
        });
        vm.tryAsyncLoad();
        vm.$emit(constant.BEFORE_LOAD, vm);
        for (var ev in vm.imageListener) {
            vm.image.addEventListener(ev, vm.imageListener[ev]);
        }
    },
    destroyed: function() {
        var vm = this;
        for (var ev in vm.imageListener) {
            vm.image.removeEventListener(ev, vm.imageListener[ev]);
        }
        vm.plugins.forEach(x => x.unbind && x.unbind(vm, vm.pluginDataSet[x.name]));
    },
    watch: {
        ratio: function(nv, ov) {
            var vm = this;
            // var dis_add = {
            //     horizontal: vm.imageSize.width * (nv - ov),
            //     vertical: vm.imageSize.height * (nv - ov)
            // };
            // vm.move(-dis_add.horizontal / 2, -dis_add.vertical / 2);
        },
        src: function() {
            var vm = this;
            vm.state = constant.UNLOAD;
            vm.thumbState = constant.UNLOAD;
            vm.tryAsyncLoad();
            vm.$emit(constant.BEFORE_LOAD, vm);
        },
        imageListener: {
            deep: true,
            handler: function(nv, ov) {
                var vm = this;
                for (var ev in ov) {
                    vm.image.removeEventListener(ev, ov[ev]);
                }
                for (var ev in nv) {
                    vm.image.addEventListener(ev, nv[ev]);
                }
            }
        },
        viewMode() {
            this.initImage();
        }
    },
    beforeUpdate: function() {
        
    },
    updated: function() {

    },
};

var dragController = {
    name: 'dragController',
    bind: function(vm, dataSet) {
        
        vm.$el.querySelector('.viewer-container').addEventListener('click', startDrag);

        dataSet.unbind = function() {
            vm.$el.querySelector('.viewer-container').removeEventListener('click', startDrag)
            unbind();
        };

        function startDrag() {
            if (dataSet.dragging) {
                return;
            }
            dataSet.dragging = true;
            vm.ignoreTransition = true;
            vm.$el.requestPointerLock();
            document.addEventListener('pointerlockchange', onPointerLockChange);
        }

        function onPointerLockChange() {
            if (document.pointerLockElement === vm.$el) {
                document.addEventListener('mousemove', dragging);
                document.addEventListener('click', endDrag);
            } else {
                endDrag();
                unbind();
            }
        }
        function dragging(e) {
            vm.move(e.movementX, e.movementY);
        }
        function endDrag() {
            dataSet.dragging = false;
            vm.ignoreTransition = false;
            document.exitPointerLock();
        }
        function unbind() {
            document.removeEventListener('pointerlockchange', onPointerLockChange);
            document.removeEventListener('mousemove', dragging);
            document.removeEventListener('click', endDrag);
        }
    },
    init: function(vm, dataSet) {
        document.exitPointerLock();
        vm.$set(dataSet, 'dragging', false);
        dataSet.dragging = false;
        vm.ignoreTransition = false;
    },
    unbind: function(vm, dataSet) {
        dataSet.unbind();
    }
};

var scaleController = {
    bind: function(vm, dataSet) {
        dataSet.changeQueue = [];
        dataSet.unbind = function() {
            vm.$el.removeEventListener('wheel', directionSwitch)
        }
        function directionSwitch(e) {
            var delta = e.wheelDelta ? (e.wheelDelta / 120) : (-e.detail / 3),
                changeQueue = dataSet.changeQueue;
            var cur;
            if (delta > 0) {
                // 向上滚
                if (!changeQueue.length) {
                    changeQueue.unshift(0.1);
                } else if (changeQueue[0] < 0) {
                    changeQueue[0] *= -1;
                } else {
                    var prev1 = changeQueue[0],
                        prev2 = changeQueue[1] || 0;
                    changeQueue.unshift(
                        Math.min(prev1 + prev2, 0.5)
                    )
                }
            } else {
                if (vm.imageWidth == vm.minWidth) {
                    return;
                }
                if (!changeQueue.length) {
                    changeQueue.unshift(-0.1);
                } else if (changeQueue[0] > 0) {
                    changeQueue[0] *= -1;
                } else {
                    var prev1 = changeQueue[0],
                        prev2 = changeQueue[1] || 0;
                    changeQueue.unshift(
                        Math.max(prev1 + prev2, -0.5)
                    )
                }
            }
            if (changeQueue.length) {
                cur = changeQueue.shift() || 0;
                var {clientX, clientY} = e;
                vm.scale(cur, {clientX, clientY});
            }
        }
        vm.$el.addEventListener('wheel', _.debounce(directionSwitch, 150, {trailing: true}))
    },
    init: function(vm, dataSet) {
        dataSet.changeQueue = [];
    },
    unbind: function(vm, dataSet) {
        dataSet.unbind();
    }
}

var resizeController = {
    bind: function(vm, dataSet) {
        var handler = _.debounce(function() {
            vm.initImage();
        }, 300, {
            trailing: true,
            maxWait: 1000
        });
        dataSet.unbind = function() {
            window.removeEventListener('resize', handler);
        }
        window.addEventListener('resize', handler);
    },
    unbind: function(vm, dataSet) {
        dataSet.unbind();
    }
}

export {scaleController, dragController, resizeController, constant}

</script>
<style type="text/css" scoped>
.image-viewer {
    position: absolute;
    width: 100%;
    height: 100%;
}

.transition {
    -webkit-transition: transform .5s, width .5s, height .5s;
       -moz-transition: transform .5s, width .5s, height .5s;
        -ms-transition: transform .5s, width .5s, height .5s;
         -o-transition: transform .5s, width .5s, height .5s;
            transition: transform .5s, width .5s, height .5s;
}

</style>